package com.isyscore.os.core.logger;


import com.isyscore.boot.logger.annotation.AccessLogger;
import com.isyscore.boot.login.LoginUserManager;
import com.isyscore.boot.login.cache.LoginUserHolder;
import com.isyscore.device.common.util.JsonMapper;
import com.isyscore.os.core.model.entity.OperationLog;
import com.isyscore.os.core.service.OperationLogService;
import com.isyscore.os.core.util.RequestUtils;
import com.isyscore.os.permission.entity.LoginVO;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.apache.catalina.connector.RequestFacade;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author felixu
 * @since 2022.04.07
 */
//@Aspect
//@Component
@RequiredArgsConstructor
public class OperationLoggerAspect {

    private final OperationLogService operationLogService;

    private final LoginUserManager loginUserManager;

    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 5,
            Runtime.getRuntime().availableProcessors() * 5, 5L,
            TimeUnit.MINUTES, new LinkedBlockingQueue<>(), cn.hutool.core.thread.ThreadFactoryBuilder.create().setNamePrefix("operation-log-")
            .setDaemon(true).build(), new ThreadPoolExecutor.DiscardPolicy());

    private static final ParameterNameDiscoverer NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    @Pointcut("@within(com.isyscore.boot.logger.annotation.AccessLogger) || @within(org.springframework.web.bind.annotation.RequestMapping)")
    public void loggerPointCut() {
    }

    @Around("loggerPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        Throwable ex = null;
        OperationLog log = new OperationLog();
        LocalDateTime requestTime = LocalDateTime.now();
        try {
            result = point.proceed();
            log.setSuccess(true);
        } catch (Throwable e) {
            log.setSuccess(false);
            ex = e;
            throw e;
        } finally {
            LocalDateTime endTime = LocalDateTime.now();
            Optional<HttpServletRequest> optional = RequestUtils.getHttpServletRequest();
            log.setErrorMsg(ex == null ? null : ex.getLocalizedMessage());
            log.setResponse(JsonMapper.toAlwaysJson(result == null ? new HashMap<>() : result));
            LoginVO user = loginUserManager.getCurrentLoginUser();
            EXECUTOR.execute(() -> loginUserManager.executeWithAssignedLoginUser(user, () -> {
                log.setRequestTime(requestTime);
                log.setDuration(Duration.between(log.getRequestTime(), endTime).toMillis());
                MethodSignature signature = (MethodSignature) point.getSignature();
                Method method = signature.getMethod();
                AccessLogger accessLogger = method.getAnnotation(AccessLogger.class);
                // 处理忽略逻辑
                if (accessLogger != null && accessLogger.ignore())
                    return null;
                ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                boolean useSelfAnn = accessLogger != null && StringUtils.hasText(accessLogger.action());
                log.setAction(useSelfAnn ? accessLogger.action() : apiOperation == null ? "" : apiOperation.value());
                log.setDescribe(useSelfAnn ? accessLogger.describe() : apiOperation == null ? "" : apiOperation.notes());
                Object[] parameters = point.getArgs();
                String[] parameterNames = NAME_DISCOVERER.getParameterNames(method);
                Map<String, Object> parameterMap = new HashMap<>(parameters.length);
                for (int i = 0; i < parameters.length; i++) {
                    Object parameter = parameters[i];
                    String parameterName = parameterNames == null || parameterNames[i] == null ? "arg" + i : parameterNames[i];
                    if (parameter instanceof RequestFacade ||
                            parameter instanceof MultipartFile){
                        parameterMap.put(parameterName, "无法被序列化的参数类型: " + parameter.getClass().getName());
                        continue;
                    }
                    parameterMap.put(parameterName, parameter);
                }
                log.setParameters(JsonMapper.toAlwaysJson(parameterMap));
                if (optional.isPresent()) {
                    HttpServletRequest request = optional.get();
                    log.setHttpHeaders(JsonMapper.toAlwaysJson(RequestUtils.getHeaders(request)));
                    log.setIp(RequestUtils.getIpAddress(request));
                    log.setHttpMethod(request.getMethod());
                    log.setUrl(request.getRequestURI());
                }
                log.setStrMethod(signature.getName());
                log.setStrTarget(point.getTarget().getClass().getName());
                log.setOperator(Optional.ofNullable(LoginUserHolder.get()).map(LoginVO::getUserId).orElse("stranger"));
//                operationLogService.save(log);
                return null;
            }));
        }
        return result;
    }
}
